package pers.xiaojun.boot.module.system.service.permission;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import pers.xiaojun.boot.common.exception.BusinessException;
import pers.xiaojun.boot.common.util.CollectionUtils;
import pers.xiaojun.boot.module.system.constants.BusinessCodeConstants;
import pers.xiaojun.boot.module.system.constants.MenuConstants;
import pers.xiaojun.boot.module.system.controller.permission.vo.menu.MenuReqVO;
import pers.xiaojun.boot.module.system.controller.permission.vo.menu.MenuRespVO;
import pers.xiaojun.boot.module.system.controller.permission.vo.menu.MenuSaveVO;
import pers.xiaojun.boot.module.system.dao.entity.tenant.TenantDO;
import pers.xiaojun.boot.module.system.dao.entity.tenant.TenantPackageDO;
import pers.xiaojun.boot.module.system.dao.mapper.permission.MenuMapper;
import pers.xiaojun.boot.module.system.dao.entity.permission.MenuDO;
import pers.xiaojun.boot.module.system.enums.permission.MenuTypeEnum;
import pers.xiaojun.boot.module.system.service.tenant.TenantPackageService;
import pers.xiaojun.boot.module.system.service.tenant.TenantService;
import pers.xiaojun.boot.tenant.core.context.TenantContextHolder;

import java.util.Collection;
import java.util.List;

import static pers.xiaojun.boot.module.system.constants.BusinessCodeConstants.MENU_NOT_EXISTS;

/**
 * 菜单服务实现类
 *
 * @author xiaojun
 * @since 2025-10-10
 */
@Service
@RequiredArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuMapper, MenuDO> implements MenuService {

    private final RoleMenuService roleMenuService;

    @Lazy
    @Autowired
    private TenantService tenantService;
    private final TenantPackageService tenantPackageService;


    @Override
    public MenuDO saveMenu(MenuSaveVO menuSave) {
        // 校验父文件夹
        validateParentMenu(menuSave.getParentId(), menuSave.getId());
        // 校验文件夹名称
        validateMenuName(menuSave.getParentId(), menuSave.getName(), menuSave.getId());
        // 校验组件名称
        validateMenuComponentName(menuSave.getComponentName(), menuSave.getId());

        // 写入
        MenuDO menuInsert = BeanUtil.toBean(menuSave, MenuDO.class);
        initMenuProperty(menuInsert);
        this.baseMapper.insert(menuInsert);
        return menuInsert;
    }

    @Override
    public MenuDO updateMenu(MenuSaveVO menuSave) {
        // 校验父文件夹
        validateParentMenu(menuSave.getParentId(), menuSave.getId());
        // 校验文件夹名称
        validateMenuName(menuSave.getParentId(), menuSave.getName(), menuSave.getId());
        // 校验组件名称
        validateMenuComponentName(menuSave.getComponentName(), menuSave.getId());

        // 写入
        MenuDO menuUpdate = BeanUtil.toBean(menuSave, MenuDO.class);
        initMenuProperty(menuUpdate);
        this.baseMapper.updateById(menuUpdate);
        return menuUpdate;
    }

    @Override
    public void removeMenuById(Long id) {
        // 判断是否存在子菜单
        if (this.baseMapper.selectCountByParentId(id) > 0) {
            throw new BusinessException(BusinessCodeConstants.MENU_EXISTS_CHILDREN);
        }
        // 判断菜单是否存在
        if (this.baseMapper.selectById(id) == null) {
            throw new BusinessException(MENU_NOT_EXISTS);
        }
        // 删除菜单
        int delCount = baseMapper.deleteById(id);
        // 删除角色菜单表中该菜单的数据
        if (delCount > 0) {
            roleMenuService.removeByMenuId(id);
        }
    }

    @Override
    public void removeMenuByIds(List<Long> ids) {
        // 判断是否有子菜单
        if (this.baseMapper.selectCountByParentIds(ids) > 0) {
            throw new BusinessException(BusinessCodeConstants.MENU_EXISTS_CHILDREN);
        }

        // 删除菜单
        int delCount = baseMapper.deleteByIds(ids);
        // 删除角色菜单表中该菜单的数据
        if (delCount > 0) {
            roleMenuService.removeByMenuIds(ids);
        }
    }

    @Override
    public MenuRespVO getMenuById(Long menuId) {
        MenuDO menu = this.baseMapper.selectById(menuId);
        if (menu == null) {
            throw new BusinessException(MENU_NOT_EXISTS);
        }
        return BeanUtil.toBean(menu, MenuRespVO.class);
    }

    @Override
    public List<Long> listMenuIdsByPermission(String permission) {
        return this.baseMapper.selectObjs(new LambdaQueryWrapper<MenuDO>()
                .select(MenuDO::getId)
                .eq(MenuDO::getPermission, permission)
        );
    }

    @Override
    public List<MenuDO> listMenus(MenuReqVO menuReqVO) {
        return this.baseMapper.selectListByQuery(menuReqVO);
    }

    @Override
    public List<MenuDO> listMenusByTenant() {
        TenantDO tenant = tenantService.getTenantById(TenantContextHolder.getTenantId());
        if (tenant.getPackageId() == 0) {
            return this.baseMapper.selectList(null);
        }
        TenantPackageDO tenantPackage = tenantPackageService.getTenantPackageById(tenant.getPackageId());
        return this.baseMapper.selectByIds(tenantPackage.getMenuIds());
    }

    @Override
    public void validateMenusExists(Collection<Long> menuIds) {
        List<MenuDO> menuList = this.baseMapper.selectByIds(menuIds);
        if (menuList.size() != menuIds.size()) {
            Sets.SetView<Long> unfoundIds = Sets.difference(
                    Sets.newHashSet(menuIds),
                    CollectionUtils.convertSet(menuList, MenuDO::getId)
            );
            throw new BusinessException(MENU_NOT_EXISTS.getCode(), StrUtil.format("菜单Id {} 不存在", unfoundIds));
        }
    }

    /**
     * 校验父文件夹
     *
     * @param parentId 父菜单Id
     * @param childId  子菜单Id
     */
    private void validateParentMenu(Long parentId, Long childId) {
        // 父菜单为空 或 为 0 表示当前菜单为父菜单
        if (parentId == null || MenuConstants.ROOT_MENU_ID.equals(parentId)) {
            return;
        }
        // 菜单不能为自己的父菜单
        if (parentId.equals(childId)) {
            throw new BusinessException(BusinessCodeConstants.MENU_PARENT_NOT_MYSELF);
        }
        // 校验父菜单是否存在
        MenuDO parentMenu = this.baseMapper.selectById(parentId);
        if (parentMenu == null) {
            throw new BusinessException(BusinessCodeConstants.MENU_PARENT_NOT_EXISTS);
        }
        // 父菜单类型只能为菜单或者目录
        if (!MenuTypeEnum.MENU.getType().equals(parentMenu.getType())
                && !MenuTypeEnum.DIR.getType().equals(parentMenu.getType())) {
            throw new BusinessException(BusinessCodeConstants.MENU_PARENT_TYPE_NOT_MATCH);
        }
    }

    /**
     * 校验菜单名称
     *
     * @param parentId 父菜单Id
     * @param menuName 菜单名称
     * @param menuId   菜单Id
     */
    private void validateMenuName(Long parentId, String menuName, Long menuId) {
        MenuDO menu = this.baseMapper.selectByParentIdAndName(parentId, menuName);
        if (menu == null) {
            return;
        }
        // 如果能查询到，并且也不是自己 则说明已存在相同的名称
        if (!menu.getId().equals(menuId)) { // menuId 放在后是因为第一次创建menuId为null
            throw new BusinessException(BusinessCodeConstants.MENU_NAME_EXISTS);
        }
    }

    /**
     * 校验菜单组件名称
     *
     * @param componentName 组件名称
     * @param menuId        菜单Id
     */
    private void validateMenuComponentName(String componentName, Long menuId) {
        if (StrUtil.isEmpty(componentName)) {
            return;
        }
        MenuDO menu = this.baseMapper.selectByComponentName(componentName);
        if (menu == null) {
            return;
        }
        // 判断是否为自己
        if (!menu.getId().equals(menuId)) {
            throw new BusinessException(BusinessCodeConstants.MENU_COMPONENT_NAME_EXISTS);
        }
    }

    /**
     * 初始化菜单的通用属性。
     *
     * @param menu 菜单
     */
    private void initMenuProperty(MenuDO menu) {
        // 菜单为按钮类型时，无需 component、icon、path 属性，进行置空
        if (MenuTypeEnum.BUTTON.getType().equals(menu.getType())) {
            menu.setComponent("");
            menu.setComponentName("");
            menu.setIcon("");
            menu.setPath("");
        }
    }

}
